package com.hontech.bread.device.mdb

import com.hontech.bread.R
import com.hontech.bread.device.Conn
import com.hontech.bread.utils.isBitSet
import com.hontech.bread.utils.log
import com.hontech.bread.utils.postEvent
import com.hontech.bread.utils.stringById
import kotlinx.coroutines.delay
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import serial.codec.FlexArray
import serial.codec.Uint16
import serial.codec.Uint32
import serial.codec.Uint8

@Serializable
data class CoinInfo(
    val value: Int,
    @SerialName("type_bit")
    val typeBit: Int,
    val number: Int,
    val full: Boolean
)

class CoinChange(
    private val coins: List<CoinInfo>,
    private val amount: Int
) {
    private var schemes: IntArray? = null
    private var minCount = Int.MAX_VALUE

    private fun change(index: Int, nums: IntArray) {

        val coin = coins[index]
        for (n in 0..coin.number) {
            nums[index] = n
            if (index == (coins.size - 1)) {
                if (finished(nums)) {
                    return
                }
            } else {
                change(index + 1, nums)
            }
        }
    }

    private fun finished(nums: IntArray): Boolean {
        var s = 0
        var count = 0
        for (i in coins.indices) {
            s += coins[i].value * nums[i]
            count += nums[i]
        }
        if (s != amount) {
            return false
        }
        if (count < minCount) {
            schemes = nums.clone()
            minCount = count
        }
        return true
    }

    private fun info(nums: IntArray) {
        log("${stringById(R.string.t1733460848_113)}")
        for (i in coins.indices) {
            log("${coins[i].value} => ${nums[i]}")
        }
    }

    fun all(): IntArray {
        val nums = IntArray(coins.size)
        change(0, nums)

        val scheme = schemes ?: throw IllegalStateException(stringById(R.string.t1733460848_114))
        info(scheme)

        return scheme
    }

}


private fun coinChangeLimited(coins: List<CoinInfo>, amount: Int): Pair<Int, List<CoinInfo>> {
    val dp =
        IntArray(amount + 1) { amount + 1 } // ${stringById(R.string.t1733460848_115)} dp ${stringById(R.string.t1733460848_116)}
    val usedCoins =
        Array(amount + 1) { IntArray(coins.size) } // ${stringById(R.string.t1733460848_117)}
    dp[0] = 0 // ${stringById(R.string.t1733460848_118)} 0 ${stringById(R.string.t1733460848_119)}

    // ${stringById(R.string.t1733460848_120)}
    for ((i, coin) in coins.withIndex()) {
        val maxCount = coin.number
        // ${stringById(R.string.t1733460848_121)}
        for (j in amount downTo 0) {
            for (k in 1..maxCount) {
                val cost = k * coin.value
                if (j >= cost && dp[j - cost] + k < dp[j]) {
                    dp[j] = dp[j - cost] + k
                    // ${stringById(R.string.t1733460848_122)}
                    usedCoins[j] = usedCoins[j - cost].clone()
                    usedCoins[j][i] += k
                }
            }
        }
    }

    // ${stringById(R.string.t1733460848_123)} dp[amount] ${stringById(R.string.t1733460848_124)}，${stringById(R.string.t1733460848_125)}
    if (dp[amount] > amount) {
        throw IllegalStateException("${stringById(R.string.t1733460848_126)}")
    }

    // ${stringById(R.string.t1733460848_127)}
    val resultCoins = coins.indices.map { i ->
        CoinInfo(coins[i].value, coins[i].typeBit, usedCoins[amount][i], false)
    }
    return Pair(dp[amount], resultCoins)
}

// ${stringById(R.string.t1733460848_128)}
data class CoinDispensed(
    val typeBit: Int,
    val num: Int,
    val tubeNum: Int,
) {

    override fun toString(): String {
        val value = CoinSetupInfo.get().valueByType(typeBit)
        return "${stringById(R.string.t1733460848_129)}${value} ${stringById(R.string.t1733460848_130)}:${num} ${
            stringById(
                R.string.t1733460848_131
            )
        }:${tubeNum}"
    }
}

// ${stringById(R.string.t1733460848_132)}
data class CoinDeposited(
    val route: Int,
    val typeBit: Int,
    val tubeNum: Int,
) {

    fun value(): Int {
        if ((route == 0) || (route == 1)) {
            return CoinSetupInfo.get().valueByType(typeBit)
        }
        return 0
    }

    override fun toString(): String {
        val position = when (route) {
            0 -> "${stringById(R.string.t1733460848_133)}"
            1 -> "${stringById(R.string.t1733460848_134)}"
            2 -> "${stringById(R.string.t1733460848_135)}"
            3 -> "${stringById(R.string.t1733460848_136)}"
            else -> "${stringById(R.string.t1733460848_137)}"
        }
        val value = CoinSetupInfo.get().valueByType(typeBit)
        return "${position}${stringById(R.string.t1733460848_138)}:${value} ${stringById(R.string.t1733460848_139)}:${tubeNum}"
    }
}

data class CoinStatus(
    val state: Int,
) {

    companion object {

        const val STATE_JUST_RESET = 11
    }

    fun isJustReset() = state == STATE_JUST_RESET

    override fun toString(): String {
        return when (state) {
            1 -> "${stringById(R.string.t1733460848_140)} - ${stringById(R.string.t1733460848_141)}"
            2 -> "${stringById(R.string.t1733460848_142)} - ${stringById(R.string.t1733460848_143)}"
            3 -> "${stringById(R.string.t1733460848_144)} - ${stringById(R.string.t1733460848_145)}，${
                stringById(
                    R.string.t1733460848_146
                )
            }"

            4 -> "${stringById(R.string.t1733460848_147)} - ${stringById(R.string.t1733460848_148)}"
            5 -> "${stringById(R.string.t1733460848_149)} - ${stringById(R.string.t1733460848_150)}，${
                stringById(
                    R.string.t1733460848_151
                )
            }"

            6 -> "${stringById(R.string.t1733460848_152)} - ${stringById(R.string.t1733460848_153)}"
            7 -> "${stringById(R.string.t1733460848_154)} - ${stringById(R.string.t1733460848_155)}"
            8 -> "ROM ${stringById(R.string.t1733460848_156)} - ${stringById(R.string.t1733460848_157)}"
            9 -> "${stringById(R.string.t1733460848_158)} - ${stringById(R.string.t1733460848_159)}，${
                stringById(
                    R.string.t1733460848_160
                )
            }"

            10 -> "${stringById(R.string.t1733460848_161)} - ${stringById(R.string.t1733460848_162)}，${
                stringById(
                    R.string.t1733460848_163
                )
            }"

            11 -> "${stringById(R.string.t1733460848_164)} - ${stringById(R.string.t1733460848_165)}"
            12 -> "${stringById(R.string.t1733460848_166)} - ${stringById(R.string.t1733460848_167)}"
            13 -> "${stringById(R.string.t1733460848_168)} - ${stringById(R.string.t1733460848_169)}"
            else -> "${stringById(R.string.t1733460848_170)}:${state and 0x1F}"
        }
    }
}

data class CoinModelInfo(
    val manufacturerCode: String,
    val serialNumber: String,
    val model: String,
    val softVersion: String,
    val option: Int
) {

    companion object {
        private var instance: CoinModelInfo? = null

        fun get(): CoinModelInfo {
            return instance ?: throw IllegalStateException(
                "${stringById(R.string.t1733460848_171)} ${
                    stringById(
                        R.string.t1733460848_172
                    )
                } ${stringById(R.string.t1733460848_173)}"
            )
        }
    }

    init {
        instance = this
    }
}

data class Diagnostic(
    val z1: Int,
    val z2: Int
) {
    init {
        log("${stringById(R.string.t1733460848_174)}:${this}")
    }

    override fun toString(): String {
        return "min:${z1} sub:${z2}"
    }
}

data class TubeStatus(
    val fullMask: Int,
    val typeBitNum: FlexArray
)

object Coin {

    private const val RESET = 0x08
    private const val SETUP = 0x09
    private const val TUBE_STATE = 0x0A
    private const val POLL = 0x0B
    private const val COIN_TYPE = 0x0C
    private const val DISPENSED = 0x0D
    private const val EXT = 0x0F

    private var resetFlag = false

    suspend fun onceReset() {
        if (!resetFlag) {
            reset(true)
        } else {
            enable()
        }
    }

    suspend fun changeable(value: Int): Pair<List<CoinInfo>, IntArray> {
        log("${stringById(R.string.t1733460848_175)}:$value")

        if (value == 0) {
            return Pair(emptyList(), intArrayOf())
        }

        val coins = queryCoinInfo()
        val ret = CoinChange(coins, value).all()
        return Pair(coins, ret)
    }

    suspend fun change(value: Int) {
        enable()

        val coins = changeable(value)

        for (index in coins.first.indices) {
            val coin = coins.first[index]
            val num = coins.second[index]
            log("${stringById(R.string.t1733460848_176)}:${coin.value} $num")
            if (num == 0) {
                continue
            }
            dispense(coin.typeBit, num)
        }
    }

    suspend fun reset(enable: Boolean = true) {
        Conn.mdbReq(RESET).assertAck()
        resetInit(enable)
        resetFlag = true
    }

    private suspend fun resetInit(enable: Boolean) {
        val info = setup()
        if (info.level == 3) {
            val model = coinModel()
            if (model.option.isBitSet(1)) {
                diagnosticStatus()
            }
        }
        delay(600)
        tubeStatus()
        if (enable) {
            val bit = info.coinTypeRouting
            coinType(bit, bit)
        } else {
            coinType(0, 0)
        }
    }

    suspend fun enable() {
        val info = setup()
        val bit = info.coinTypeRouting
        coinType(bit, bit)
    }

    suspend fun disable() {
        coinType(0, 0)
    }

    private suspend fun coinType(accept: Int, dispense: Int) {
        Conn.mdbReq(COIN_TYPE, Uint16(accept), Uint16(dispense)).isAck()
    }

    private suspend fun tubeStatus(): TubeStatus {
        val z1_2 = Uint16()
        val typeBitNum = FlexArray(16)
        Conn.mdbReq(TUBE_STATE).parse(z1_2, typeBitNum)
        return TubeStatus(z1_2.value, typeBitNum)
    }

    private suspend fun diagnosticStatus() {
        val frame = Conn.mdbReq(EXT, Uint8(5))
        val len = frame.dataLen()
        var index = 0
        while (index < len) {
            val z1 = frame.dataGet(index)
            val z2 = frame.dataGet(index + 1)
            Diagnostic(z1, z2)
            index += 2
        }
    }

    // ${stringById(R.string.t1733460848_177)} ${stringById(R.string.t1733460848_178)} ${stringById(R.string.t1733460848_179)}
    private suspend fun coinModel(): CoinModelInfo {
        val z1_3 = FlexArray(3)
        val z4_15 = FlexArray(12)
        val z16_27 = FlexArray(12)
        val z28_29 = FlexArray(2)
        val z30_33 = Uint32()
        Conn.mdbReq(EXT, Uint8(0)).parse(z1_3, z4_15, z16_27, z28_29, z30_33)
        return CoinModelInfo(
            z1_3.toString(),
            z4_15.toString(),
            z16_27.toString(),
            z28_29.toBCD(),
            z30_33.value
        )
    }

    suspend fun setup(): CoinSetupInfo {
        val z1 = Uint8()
        val z2_3 = Uint16()
        val z4 = Uint8()
        val z5 = Uint8()
        val z6_7 = Uint16()
        val z8_23 = FlexArray(16)
        Conn.mdbReq(SETUP).parse(z1, z2_3, z4, z5, z6_7, z8_23)
        val info = CoinSetupInfo(
            z1.value,
            z2_3.value,
            z4.value,
            z5.value,
            z6_7.value,
            z8_23
        )
        return info
    }

    suspend fun queryCoinInfo(): List<CoinInfo> {
        val infos = ArrayList<CoinInfo>()
        val setupInfo = setup()
        val tube = tubeStatus()
        for (i in 0 until tube.typeBitNum.len) {
            if (!setupInfo.coinTypeRouting.isBitSet(i)) {
                continue
            }
            val value = setupInfo.valueByType(i)
            val full = tube.fullMask.isBitSet(i)
            val num = tube.typeBitNum.getData(i)
            val info = CoinInfo(value, i, num, full)
            infos.add(info)
        }
        return infos
    }

    suspend fun dispense(typeBit: Int, number: Int) {
        var s = 0

        while (s < number) {
            var n = number - s
            if (n > 0x0F) {
                n = 0x0F
            }
            Conn.mdbReq(DISPENSED, Uint8(typeBit + (n shl 4))).assertAck()
            while (true) {
                val frame = Conn.mdbReq(POLL)
                if (frame.isAck()) {
                    break
                }
                delay(500)
            }
            s += n
        }
    }

    fun onPollNotify(frame: MdbFrame) {
        val dataLen = frame.dataLen()
        val head = frame.dataGet(0)

        if (dataLen == 2) {
            if ((head and 0x80) != 0) { // ${stringById(R.string.t1733460848_180)}
                val num = (head shr 4) and 0x07
                val typeBit = head and 0x0F
                val z = frame.dataGet(1)
                val state = CoinDispensed(typeBit, num, z)
                postEvent(state)
            } else if ((head and 0xC0) == 0x40) {
                val route = (head shr 4) and 0x03
                val typeBit = head and 0x0F
                val z = frame.dataGet(1)
                val state = CoinDeposited(route, typeBit, z)
                postEvent(state)
            }
        } else if (dataLen == 1) {
            if ((head <= 13) || ((head and 0x20) == 0x20)) {
                val state = CoinStatus(head)
                postEvent(state)
            }
        }

    }

}




















